import numpy as np
import pandas as pd
import random
import matplotlib.pyplot as plt


class Logistic:
    """
    这是一个logistic二分类模型
    """
    def __init__(self):
        # 定义二分类数据集，其中：x仅有一个特征
        self.dataset_x = np.array([0.1, 0.33, 0.4, 1.5, 2.6, 5, 6.6, 7, 9, 11.3, 11.5, 15])
        self.dataset_y = np.array([0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1])
        # 定义x参数θ0,θ1
        self.a = 0
        self.b = 0
        # 定义模型参数
        self.r = 0.15                                    # 学习率r
        self.n = len(self.dataset_x)                    # 样本数量n
        self.m = 1                                      # 样本特征数m
        self.EPOCH = 20000                                 # 迭代次数

    def h(self, x):
        """
        logistic二分类假设函数: h(x)=1/(1+exp(-z)), z=θ0+θ1*x1+θ2*x2+...+θm*xm.(可简写为向量积形式)
        :param x: 输入自变量x数组
        :return: 预测值数组
        """
        y = 1 / (1+np.exp(-(self.a+self.b*x)))
        return y

    def cost(self, x, y):
        """
        损失函数(不同于代价函数J): cost = -y*ln(h(x)) - (1-y)*ln(1-h(x))
        :param x: 输入自变量x
        :param y: 输入人工标记值y数组
        :return: 损失值(0~1)数组
        """
        h = self.h(x)
        cost = -y*np.log(h) - (1-y)*np.log(1-h)
        return cost

    def gradient(self, x, y):
        """
        梯度下降算法: θj = θj - α*(dJ/dθj)
        :param y:输入人工标记y数组
        :param x:输入自变量x数组(x0=1)
        :return:无返回值,更新参数
        """
        temp1 = (1/self.n) * np.sum((self.h(x)-y)*1)
        temp2 = (1/self.n) * np.sum((self.h(x)-y)*x)
        self.a = self.a - self.r*temp1
        self.b = self.b - self.r*temp2

    def training(self, x, y):
        """
        训练模型
        :param x: 输入自变量x
        :param y: 输入人工标记y
        :return: 无返回值
        """
        for i in range(self.EPOCH):
            self.gradient(x, y)
            lost = 1/self.n * np.sum(self.cost(x, y))
            print(f"第{i}次迭代损失值为:{lost}, 参数: a={self.a}, b={self.b}")

    def display(self, x, y):
        plt.scatter(x=x, y=y)
        newx = np.arange(0, 20, 0.01)
        newy = self.h(newx)
        plt.plot(newx, newy)
        plt.show()


A = Logistic()
A.training(A.dataset_x, A.dataset_y)
print(A.h(A.dataset_x))
A.display(A.dataset_x, A.dataset_y)
